/*
 * MIT License
 *
 * Copyright (c) 2024-2048 冰羽
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package cn.star.framework.gen.util;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.star.framework.controller.AbstractController;
import cn.star.framework.core.IEntity;
import cn.star.framework.core.IRepository;
import cn.star.framework.core.IService;
import cn.star.framework.core.IdStrategyValue;
import cn.star.framework.core.api.AppResult;
import cn.star.framework.gen.core.AbstractJavaCodeModel;
import cn.star.framework.gen.model.ControllerModel;
import cn.star.framework.gen.model.EntityModel;
import cn.star.framework.gen.model.RepositoryModel;
import cn.star.framework.gen.model.ServiceImplModel;
import cn.star.framework.gen.model.ServiceModel;
import cn.star.framework.gen.model.field.EntityField;
import cn.star.framework.gen.model.field.TableColumnInfo;
import cn.star.framework.service.AbstractService;
import cn.star.framework.strategy.IdStrategyUuidValue;
import cn.star.framework.util.Keyword;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.Lob;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.UniqueConstraint;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.mapping.Constraint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

/**
 * 模板静态函数<br>
 * 支持在模板中直接调用的函数<br>
 *
 * @author zhaoweiping
 *     <p style='color: red'>Created on 2024-09-23 11:06:00
 * @since 3.0.0
 */
@Slf4j
public final class TemplateUtil {

  /**
   * 字符串转小写
   *
   * @param str 字符串
   * @return
   */
  private static String wrapperStrLowerCase(String str) {
    return str == null ? null : str.toLowerCase(Locale.ROOT);
  }

  /**
   * 生成{@link IEntity}子类代码
   *
   * @param model {@link EntityModel}
   * @return
   */
  public static String getEntityCode(EntityModel model) {
    StringBuffer sb = new StringBuffer();
    sb.append(StrUtil.format("package {};\n\n", model.getPackageName()));

    sb.append(wrapperClassDescribe(model));

    sb.append(StrUtil.format("@{}\n", Data.class.getName()));
    sb.append(StrUtil.format("@{}\n", Entity.class.getName()));
    sb.append(wrapperTableAnnotation(model));
    sb.append(wrapperTableCommentAnnotation(model));

    sb.append(
        StrUtil.format(
            "public class {} {} {} {\n",
            model.getName(),
            wrapperExtendsKeyword(model),
            model.getSuperEntityClass().getName()));

    sb.append(wrapperEntityFields(model));

    sb.append(StrUtil.format("}"));

    return sb.toString();
  }

  /**
   * 生成{@link IRepository}子类代码
   *
   * @param model {@link RepositoryModel}
   * @return
   */
  public static String getRepositoryCode(RepositoryModel model) {
    StringBuffer sb = new StringBuffer();
    sb.append(StrUtil.format("package {};\n\n", model.getPackageName()));

    sb.append(wrapperClassDescribe(model));

    sb.append(StrUtil.format("@{}\n", Repository.class.getName()));

    sb.append(
        StrUtil.format(
            "public interface {} extends {}<{}, {}> {\n\n",
            model.getName(),
            IRepository.class.getName(),
            wrapperClassName(model.getEntity()),
            wrapperEntityPrimaryKeyClassName(model.getEntity())));

    sb.append(StrUtil.format("}"));

    return sb.toString();
  }

  /**
   * 生成{@link IService}子类代码
   *
   * @param model {@link ServiceModel}
   * @return
   */
  public static String getServiceCode(ServiceModel model) {
    StringBuffer sb = new StringBuffer();
    sb.append(StrUtil.format("package {};\n\n", model.getPackageName()));

    sb.append(wrapperClassDescribe(model));

    sb.append(
        StrUtil.format(
            "public interface {} extends {}<{}, {}> {\n\n",
            model.getName(),
            IService.class.getName(),
            wrapperClassName(model.getEntity()),
            wrapperEntityPrimaryKeyClassName(model.getEntity())));

    sb.append(StrUtil.format("}"));

    return sb.toString();
  }

  /**
   * 生成{@link AbstractService}、{@link #getServiceCode(ServiceModel)}子类代码
   *
   * @param model {@link ServiceImplModel}
   * @return
   */
  public static String getServiceImplCode(ServiceImplModel model) {
    StringBuffer sb = new StringBuffer();
    sb.append(StrUtil.format("package {};\n\n", model.getPackageName()));

    sb.append(wrapperClassDescribe(model));

    sb.append(StrUtil.format("@{}\n", Service.class.getName()));

    sb.append(
        StrUtil.format(
            "public class {} extends {}<{}, {}, {}> implements {} {\n",
            model.getName(),
            AbstractService.class.getName(),
            wrapperClassName(model.getEntity()),
            wrapperEntityPrimaryKeyClassName(model.getEntity()),
            wrapperClassName(model.getRepository()),
            wrapperClassName(model.getService())));

    sb.append(
        StrUtil.format(
            "  public {}({} repository) {\n",
            model.getName(),
            wrapperClassName(model.getRepository())));
    sb.append(StrUtil.format("    super(repository);\n"));
    sb.append(StrUtil.format("  }\n"));

    sb.append(StrUtil.format("}"));

    return sb.toString();
  }

  private static StringBuffer getControllerFnCodeSave(ControllerModel model) {
    StringBuffer sb = new StringBuffer();
    sb.append(StrUtil.format("  @{}\n", Override.class.getName()));
    sb.append(StrUtil.format("  @{}\n", ResponseBody.class.getName()));
    sb.append(StrUtil.format("  @{}\n", PostMapping.class.getName()));
    sb.append(
        StrUtil.format(
            "  public {} save(@{} {} entity) {\n",
            AppResult.class.getName(),
            RequestBody.class.getName(),
            wrapperClassName(model.getEntity())));

    if (model.isThrowable()) {
      sb.append(StrUtil.format("    return super.save(entity);\n"));
    } else {
      sb.append(StrUtil.format("    try {\n"));
      sb.append(StrUtil.format("      return super.save(entity);\n"));
      sb.append(StrUtil.format("    } catch (Exception e) {\n"));
      sb.append(StrUtil.format("      return super.ofFail(e.getMessage());\n"));
      sb.append(StrUtil.format("    }\n"));
    }

    sb.append(StrUtil.format("  }\n\n"));

    return sb;
  }

  private static StringBuffer getControllerFnCodeUpdate(ControllerModel model) {
    StringBuffer sb = new StringBuffer();
    sb.append(StrUtil.format("  @{}\n", Override.class.getName()));
    sb.append(StrUtil.format("  @{}\n", ResponseBody.class.getName()));
    sb.append(StrUtil.format("  @{}\n", PutMapping.class.getName()));
    sb.append(
        StrUtil.format(
            "  public {} update(@{} {} entity) {\n",
            AppResult.class.getName(),
            RequestBody.class.getName(),
            wrapperClassName(model.getEntity())));

    if (model.isThrowable()) {
      sb.append(StrUtil.format("    return super.update(entity);\n"));
    } else {
      sb.append(StrUtil.format("    try {\n"));
      sb.append(StrUtil.format("      return super.update(entity);\n"));
      sb.append(StrUtil.format("    } catch (Exception e) {\n"));
      sb.append(StrUtil.format("      return super.ofFail(e.getMessage());\n"));
      sb.append(StrUtil.format("    }\n"));
    }

    sb.append(StrUtil.format("  }\n\n"));

    return sb;
  }

  private static StringBuffer getControllerFnCodeDelete(ControllerModel model) {
    StringBuffer sb = new StringBuffer();
    sb.append(StrUtil.format("  @{}\n", Override.class.getName()));
    sb.append(StrUtil.format("  @{}\n", ResponseBody.class.getName()));
    sb.append(StrUtil.format("  @{}\n", DeleteMapping.class.getName()));
    sb.append(
        StrUtil.format(
            "  public {} deleteByIds(@{} {}... ids) {\n",
            AppResult.class.getName(),
            RequestBody.class.getName(),
            wrapperEntityPrimaryKeyClassName(model.getEntity())));

    if (model.isThrowable()) {
      sb.append(StrUtil.format("    return super.deleteByIds(ids);\n"));
    } else {
      sb.append(StrUtil.format("    try {\n"));
      sb.append(StrUtil.format("      return super.deleteByIds(ids);\n"));
      sb.append(StrUtil.format("    } catch (Exception e) {\n"));
      sb.append(StrUtil.format("      return super.ofFail(e.getMessage());\n"));
      sb.append(StrUtil.format("    }\n"));
    }

    sb.append(StrUtil.format("  }\n\n"));

    return sb;
  }

  private static StringBuffer getControllerFnCodeList(ControllerModel model) {
    StringBuffer sb = new StringBuffer();
    sb.append(StrUtil.format("  @{}\n", ResponseBody.class.getName()));
    sb.append(StrUtil.format("  @{}\n", GetMapping.class.getName()));
    sb.append(
        StrUtil.format(
            "  public {} list({} request) {\n",
            AppResult.class.getName(),
            HttpServletRequest.class.getName()));

    if (model.isThrowable()) {
      sb.append(
          StrUtil.format(
              "    return super.list(request, {}.class);\n",
              wrapperClassName(model.getEntity())));
    } else {
      sb.append(StrUtil.format("    try {\n"));
      sb.append(
          StrUtil.format(
              "      return super.list(request, {}.class);\n",
              wrapperClassName(model.getEntity())));
      sb.append(StrUtil.format("    } catch (Exception e) {\n"));
      sb.append(StrUtil.format("      return super.ofFail(e.getMessage());\n"));
      sb.append(StrUtil.format("    }\n"));
    }

    sb.append(StrUtil.format("  }\n\n"));

    return sb;
  }

  private static StringBuffer getControllerFnCodeGet(ControllerModel model) {
    StringBuffer sb = new StringBuffer();
    sb.append(StrUtil.format("  @{}\n", Override.class.getName()));
    sb.append(StrUtil.format("  @{}\n", ResponseBody.class.getName()));
    sb.append(StrUtil.format("  @{}(\"/{id}\")\n", GetMapping.class.getName()));
    sb.append(
        StrUtil.format(
            "  public {} get(@{}(value = \"id\") {} id) {\n",
            AppResult.class.getName(),
            PathVariable.class.getName(),
            wrapperEntityPrimaryKeyClassName(model.getEntity())));

    if (model.isThrowable()) {
      sb.append(StrUtil.format("    return super.get(id);\n"));
    } else {
      sb.append(StrUtil.format("    try {\n"));
      sb.append(StrUtil.format("      return super.get(id);\n"));
      sb.append(StrUtil.format("    } catch (Exception e) {\n"));
      sb.append(StrUtil.format("      return super.ofFail(e.getMessage());\n"));
      sb.append(StrUtil.format("    }\n"));
    }

    sb.append(StrUtil.format("  }\n\n"));

    return sb;
  }

  private static StringBuffer getControllerFnCodePageable(ControllerModel model) {
    StringBuffer sb = new StringBuffer();
    sb.append(StrUtil.format("  @{}\n", ResponseBody.class.getName()));
    sb.append(StrUtil.format("  @{}(\"/page\")\n", GetMapping.class.getName()));
    sb.append(
        StrUtil.format(
            "  public {} pageable({} request) {\n",
            AppResult.class.getName(),
            HttpServletRequest.class.getName()));

    if (model.isThrowable()) {
      sb.append(
          StrUtil.format(
              "    return super.pageable(request, {}.class);\n",
              wrapperClassName(model.getEntity())));
    } else {
      sb.append(StrUtil.format("    try {\n"));
      sb.append(
          StrUtil.format(
              "      return super.pageable(request, {}.class);\n",
              wrapperClassName(model.getEntity())));
      sb.append(StrUtil.format("    } catch (Exception e) {\n"));
      sb.append(StrUtil.format("      return super.ofFail(e.getMessage());\n"));
      sb.append(StrUtil.format("    }\n"));
    }

    sb.append(StrUtil.format("  }\n\n"));

    return sb;
  }

  private static StringBuffer getControllerFnCodeImport(ControllerModel model) {
    StringBuffer sb = new StringBuffer();
    sb.append(StrUtil.format("  @{}\n", ResponseBody.class.getName()));
    sb.append(StrUtil.format("  @{}(\"/imp\")\n", PostMapping.class.getName()));
    sb.append(
        StrUtil.format(
            "  public {} imp({} request, @{}(\"file\") {} file) {\n",
            AppResult.class.getName(),
            HttpServletRequest.class.getName(),
            RequestParam.class.getName(),
            MultipartFile.class.getName()));

    if (model.isThrowable()) {
      sb.append(
          StrUtil.format(
              "    return super.imp(\"{}\", request, file, {}.class);\n",
              wrapperDefaultValue(model.getReadWriteTmpDir(), "/tmp"),
              wrapperClassName(model.getEntity())));
    } else {
      sb.append(StrUtil.format("    try {\n"));
      sb.append(
          StrUtil.format(
              "      return super.imp(\"{}\", request, file, {}.class);\n",
              wrapperDefaultValue(model.getReadWriteTmpDir(), "/tmp"),
              wrapperClassName(model.getEntity())));
      sb.append(StrUtil.format("    } catch (Exception e) {\n"));
      sb.append(StrUtil.format("      return super.ofFail(e.getMessage());\n"));
      sb.append(StrUtil.format("    }\n"));
    }

    sb.append(StrUtil.format("  }\n\n"));

    return sb;
  }

  private static StringBuffer getControllerFnCodeExport(ControllerModel model) {
    StringBuffer sb = new StringBuffer();
    sb.append(StrUtil.format("  @{}(\"/exp\")\n", GetMapping.class.getName()));
    sb.append(
        StrUtil.format(
            "  public void exp({} request, {} response) {\n",
            HttpServletRequest.class.getName(),
            HttpServletResponse.class.getName()));
    sb.append(
        StrUtil.format(
            "    super.exp(request, response, {}.class);\n", wrapperClassName(model.getEntity())));
    sb.append(StrUtil.format("  }\n\n"));

    return sb;
  }

  /**
   * 生成{@link AbstractController}子类代码
   *
   * @param model {@link ControllerModel}
   * @return
   */
  public static String getControllerCode(ControllerModel model) {
    StringBuffer sb = new StringBuffer();
    sb.append(StrUtil.format("package {};\n\n", model.getPackageName()));

    sb.append(wrapperClassDescribe(model));

    sb.append(StrUtil.format("@{}\n", RestController.class.getName()));
    sb.append(
        StrUtil.format("@{}(value = \"{}\")\n", RequestMapping.class.getName(), model.getRest()));

    sb.append(
        StrUtil.format(
            "public class {} extends {}<{}, {}> {\n",
            model.getName(),
            AbstractController.class.getName(),
            wrapperClassName(model.getEntity()),
            wrapperEntityPrimaryKeyClassName(model.getEntity())));
    sb.append(
        StrUtil.format(
            "  @{} @{} private {} service;\n\n",
            Autowired.class.getName(),
            Getter.class.getName(),
            wrapperClassName(model.getService())));

    // 方法：新增数据
    sb.append(getControllerFnCodeSave(model));

    // 方法：更新数据
    sb.append(getControllerFnCodeUpdate(model));

    // 方法：删除数据
    sb.append(getControllerFnCodeDelete(model));

    // 方法：数据查询(列表)
    sb.append(getControllerFnCodeList(model));

    // 方法：数据查询(Id)
    sb.append(getControllerFnCodeGet(model));

    // 方法：数据查询(分页)
    sb.append(getControllerFnCodePageable(model));

    // 方法：数据导入
    sb.append(getControllerFnCodeImport(model));

    // 方法：数据导出
    sb.append(getControllerFnCodeExport(model));

    sb.append(StrUtil.format("}"));

    return sb.toString();
  }

  /**
   * 包装类描述
   *
   * @param model {@link AbstractJavaCodeModel}
   * @return
   */
  private static String wrapperClassDescribe(AbstractJavaCodeModel model) {
    StringBuffer sb = new StringBuffer();
    String describe = StrUtil.isBlank(model.getDescribe()) ? model.getName() : model.getDescribe();

    sb.append("/**\n");
    sb.append(StrUtil.format(" * {}<br>\n", describe));
    sb.append(StrUtil.format(" * \n"));
    sb.append(StrUtil.format(" * @author {}\n", model.getAuthor()));
    sb.append(StrUtil.format(" *     <p style='color: red'>Created on {}\n", model.getDate()));
    sb.append(StrUtil.format(" * @since  {}\n", model.getSince()));
    sb.append(" */\n");

    return sb.toString();
  }

  /**
   * 包装全包的类名
   *
   * @param model {@link AbstractJavaCodeModel}
   * @return
   */
  private static String wrapperClassName(AbstractJavaCodeModel model) {
    return StrUtil.format("{}.{}", model.getPackageName(), model.getName());
  }

  /**
   * 包装类型默认值
   *
   * @param value
   * @param defaultValue
   * @param <T>
   * @return
   */
  private static <T> T wrapperDefaultValue(T value, T defaultValue) {
    return value == null ? defaultValue : value;
  }

  /**
   * 包装全包的类名<br>
   * 支持数组
   *
   * @param clazz 类型
   * @return
   */
  private static String wrapperClassType(Class<?> clazz) {
    if (clazz.isArray()) {
      Class<?> type = clazz.getComponentType();
      return StrUtil.format("{}[]", type.getName());
    }
    return clazz.getName();
  }

  /**
   * 包装实体对应表主键全包名类名
   *
   * @param model {@link EntityModel}
   * @return
   */
  private static String wrapperEntityPrimaryKeyClassName(EntityModel model) {
    List<String> columns = model.getPrimaryKeyColumns();
    if (CollectionUtil.isNotEmpty(columns)) {
      String name = columns.get(0);
      List<EntityField> entityFields = model.getFields();
      EntityField field =
          entityFields.stream().filter(i -> name.equals(i.getName())).findFirst().orElse(null);
      if (field != null) {
        return wrapperClassType(field.getType());
      }
    }
    Class<? extends IEntity> superEntityClass = model.getSuperEntityClass();
    List<Field> fields = getClassFields(superEntityClass);
    Field field =
        fields.stream().filter(i -> i.getAnnotation(Id.class) != null).findFirst().orElse(null);

    return field == null ? String.class.getName() : field.getType().getName();
  }

  /**
   * 包装继承还是实现关键字
   *
   * @param model {@link EntityModel}
   * @return
   */
  private static String wrapperExtendsKeyword(EntityModel model) {
    return model.getSuperEntityClass().isInterface() ? "implements" : "extends";
  }

  /**
   * 包装实体{@link Table}注解
   *
   * @param model {@link EntityModel}
   * @return
   */
  private static String wrapperTableAnnotation(EntityModel model) {
    StringBuffer sb =
        new StringBuffer(
            StrUtil.format(
                "@{}(name = \"{}\"", Table.class.getName(), wrapperStrLowerCase(model.getTable())));

    List<EntityField> fields = model.getFields();
    wrapperTablePrimaryKeys(model);

    Map<String, List<String>> indexesMap = model.getIndexesColumnMap();
    if (CollectionUtil.isNotEmpty(indexesMap)) {
      List<String> indexes = new ArrayList<>();
      Set<String> keys = indexesMap.keySet();
      for (String key : keys) {
        List<String> fieldNames = indexesMap.get(key);
        List<EntityField> indexColumns =
            fields.stream()
                .filter(i -> fieldNames.contains(i.getName()))
                .collect(Collectors.toList());
        String code =
            StrUtil.format(
                "@{}(name = \"{}\", columnList = \"{}\")",
                Index.class.getName(),
                wrapperStrLowerCase(generateName("index_", model, indexColumns)),
                wrapperStrLowerCase(
                    fields.stream()
                        .filter(i -> fieldNames.contains(i.getName()))
                        .map(i -> i.getColumn().getName())
                        .collect(Collectors.joining(", "))));
        indexes.add(code);
      }
      if (CollectionUtil.isNotEmpty(indexes)) {
        sb.append(", indexes = {").append(String.join(", ", indexes)).append("}");
      }
    }

    Map<String, List<String>> uniquesMap = model.getUniquesColumnMap();
    if (CollectionUtil.isNotEmpty(uniquesMap)) {
      List<String> uniqueConstraints = new ArrayList<>();
      Set<String> keys = uniquesMap.keySet();
      for (String key : keys) {
        List<String> fieldNames = uniquesMap.get(key);
        List<EntityField> uniqueColumns =
            fields.stream()
                .filter(i -> fieldNames.contains(i.getName()))
                .collect(Collectors.toList());
        String code =
            StrUtil.format(
                "@{}(name = \"{}\", columnNames = {{}})",
                UniqueConstraint.class.getName(),
                wrapperStrLowerCase(generateName("unique_", model, uniqueColumns)),
                fields.stream()
                    .filter(i -> fieldNames.contains(i.getName()))
                    .map(i -> "\"" + wrapperStrLowerCase(i.getColumn().getName()) + "\"")
                    .collect(Collectors.joining(", ")));
        uniqueConstraints.add(code);
      }
      if (CollectionUtil.isNotEmpty(uniqueConstraints)) {
        sb.append(", uniqueConstraints = {")
            .append(String.join(", ", uniqueConstraints))
            .append("}");
      }
    }
    sb.append(")\n");

    return sb.toString();
  }

  /**
   * 包装实体{@link org.hibernate.annotations.Table}注解
   *
   * @param model {@link EntityModel}
   * @return
   */
  private static String wrapperTableCommentAnnotation(EntityModel model) {
    StringBuffer sb = new StringBuffer();
    String TEMPLATE_TABLE = "@{}(appliesTo = \"{}\", comment = \"{}\")\n";

    String comment = model.getComment();
    if (StrUtil.isBlank(comment)) {
      comment = model.getDescribe();
    }
    if (StrUtil.isNotBlank(comment)) {
      sb.append(
          StrUtil.format(
              TEMPLATE_TABLE,
              org.hibernate.annotations.Table.class.getName(),
              wrapperStrLowerCase(model.getTable()),
              comment));
    }

    return sb.toString();
  }

  /**
   * 包装主键<br>
   * 若主键是联合主键则自动转换为唯一约束
   *
   * @param model {@link EntityModel}
   */
  private static void wrapperTablePrimaryKeys(EntityModel model) {
    List<String> primaryKeys = model.getPrimaryKeyColumns();
    String compositeKeysName = "union_primary_keys_", idFileName = "id", idColumnName = "id_";

    if (primaryKeys.size() > 1 && !model.getUniquesColumnMap().containsKey(compositeKeysName)) {
      List<String> uniqueColumns =
          primaryKeys.stream()
              .filter(i -> !idFileName.equalsIgnoreCase(i))
              .collect(Collectors.toList());

      model.addUniqueColumns(compositeKeysName, ArrayUtil.toArray(uniqueColumns, String.class));

      primaryKeys.removeAll(uniqueColumns);

      if (primaryKeys.isEmpty()) {
        // 构建一个主键，自动产生
        EntityField pk =
            new EntityField(idFileName)
                .describe("主键")
                .type(String.class)
                .idStrategyClass(IdStrategyUuidValue.class);
        pk.getColumn().name(idColumnName).length(32).comment("主键");

        primaryKeys.add(pk.getName());
        model.addEntityFields(pk);
      }
    }
  }

  /**
   * 包装实体字段{@link Column}注解
   *
   * @param model {@link EntityModel}
   * @param field {@link EntityField}
   * @return
   */
  private static List<String> wrapperColumnAnnotation(EntityModel model, EntityField field) {
    List<String> codes = new ArrayList<>(1);
    TableColumnInfo column = field.getColumn();

    String TEMPLATE_ID = "@" + Id.class.getName() + "\n";
    String TEMPLATE_ID_GENERATED_VALUE =
        "@" + GeneratedValue.class.getName() + "(generator = {}.STRATEGY)\n";
    String TEMPLATE_ID_GENERIC_GENERATOR =
        "@" + GenericGenerator.class.getName() + "(name = {}.STRATEGY, strategy = {}.STRATEGY)\n";
    String TEMPLATE_LOB = "@" + Lob.class.getName() + "\n";
    String TEMPLATE_COLUMN =
        "@"
            + Column.class.getName()
            + "(name = \"{}\", unique = {}, nullable = {}, insertable = {}, updatable = {}, table = \"{}\", length = {}, precision = {}, scale = {})\n";
    String TEMPLATE_TRANSIENT = StrUtil.format("@{}\n", Transient.class.getName());

    if (field.isPersistence()) {
      List<String> primaryKeys = model.getPrimaryKeyColumns();
      if (primaryKeys.size() == 1 && primaryKeys.contains(field.getName())) {
        codes.add(TEMPLATE_ID);
        Class<? extends IdStrategyValue> idStrategyClass = field.getIdStrategyClass();
        if (idStrategyClass != null) {
          codes.add(StrUtil.format(TEMPLATE_ID_GENERATED_VALUE, idStrategyClass.getName()));
          codes.add(
              StrUtil.format(
                  TEMPLATE_ID_GENERIC_GENERATOR,
                  idStrategyClass.getName(),
                  idStrategyClass.getName()));
        }
      }
      if (field.isLob()) {
        codes.add(TEMPLATE_LOB);
      }
      codes.add(
          StrUtil.format(
              TEMPLATE_COLUMN,
              wrapperStrLowerCase(column.getName()),
              column.isUnique(),
              column.isNullable(),
              column.isInsertable(),
              column.isUpdatable(),
              wrapperStrLowerCase(model.getTable()),
              column.getLength(),
              column.getPrecision(),
              column.getScale()));
    } else {
      codes.add(TEMPLATE_TRANSIENT);
    }

    return codes;
  }

  /**
   * 包装实体字段{@link Comment}注解
   *
   * @param field {@link EntityField}
   * @return
   */
  private static String wrapperColumnCommentAnnotation(EntityField field) {
    TableColumnInfo column = field.getColumn();
    String TEMPLATE_COMMENT = "@" + Comment.class.getName() + "(value = \"{}\")\n";
    StringBuffer sb = new StringBuffer();
    if (StrUtil.isNotBlank(column.getComment())) {
      sb.append(StrUtil.format(TEMPLATE_COMMENT, column.getComment()));
    } else if (StrUtil.isNotBlank(field.getDescribe())) {
      sb.append(StrUtil.format(TEMPLATE_COMMENT, field.getDescribe()));
    }

    return sb.toString();
  }

  /**
   * 包装实体字段注释
   *
   * @param field {@link EntityField}
   * @return
   */
  private static String wrapperEntityFieldDescribe(EntityField field) {
    String describe = field.getDescribe();
    return StrUtil.format("/** {} */\n", describe == null ? "" : describe);
  }

  /**
   * 包装Java关键字
   *
   * @param str 关键字
   * @return
   */
  private static String wrapperJavaKeyword(String str) {
    return Keyword.isKeyword(str) ? StrUtil.format("{}_", str) : str;
  }

  /**
   * 包装实体字段
   *
   * @param field {@link EntityField}
   * @return
   */
  private static String wrapperEntityField(EntityField field) {
    return StrUtil.format(
        "private {} {};\n", wrapperClassType(field.getType()), wrapperJavaKeyword(field.getName()));
  }

  /**
   * 包装实体字段列表
   *
   * @param model {@link EntityModel}
   * @return
   */
  private static String wrapperEntityFields(EntityModel model) {
    StringBuffer sb = new StringBuffer();
    List<EntityField> fields = model.getFields();
    Class<? extends IEntity> superEntityClass = model.getSuperEntityClass();
    List<Field> superEntityFields = getClassFields(superEntityClass);
    for (int i = 0; i < fields.size(); i++) {
      EntityField field = fields.get(i);
      boolean exist =
          superEntityFields.stream()
                  .filter(item -> item.getName().equals(field.getName()))
                  .findFirst()
                  .orElse(null)
              != null;
      if (exist) {
        log.warn(
            "Field: {} already exists in parent class: {}, please check",
            superEntityClass.getName(),
            field.getName());
        continue;
      }

      List<String> codes = new ArrayList<>(4);

      codes.add(wrapperEntityFieldDescribe(field));
      codes.addAll(wrapperColumnAnnotation(model, field));
      codes.add(wrapperColumnCommentAnnotation(field));
      codes.add(wrapperEntityField(field));

      // google java code format "  "
      codes.forEach(str -> sb.append(StrUtil.isNotBlank(str) ? "  " : "").append(str));

      if (i != fields.size() - 1) {
        sb.append("\n");
      }
    }

    return sb.toString();
  }

  /**
   * 获取类中的所有字段列表
   *
   * @param clazz {@link Class}
   * @return
   */
  private static List<Field> getClassFields(Class<?> clazz) {
    List<Field> list = new ArrayList<>();
    Field[] fields = ReflectUtil.getFields(clazz);
    if (ArrayUtil.isNotEmpty(fields)) {
      list = Arrays.asList(fields);
    }

    return list;
  }

  /**
   * {@link Constraint#generateName}
   *
   * @param prefix
   * @param model
   * @param entityFields
   * @return
   */
  private static String generateName(
      String prefix, EntityModel model, List<EntityField> entityFields) {
    // Use a concatenation that guarantees uniqueness, even if identical names
    // exist between all table and column identifiers.

    StringBuilder sb = new StringBuilder("table`" + model.getTable() + "`");

    // Ensure a consistent ordering of columns, regardless of the order
    // they were bound.
    // Clone the list, as sometimes a set of order-dependent Column
    // bindings are given.
    for (EntityField column : entityFields) {
      sb.append("column`").append(column.getColumn().getName()).append("`");
    }
    return prefix + Constraint.hashedName(sb.toString());
  }
}
